use std::convert::Infallible;
use std::net::SocketAddr;
use hyper::{Body, Request, Response, Server};
use hyper::service::{make_service_fn, service_fn};
use prometheus::{Encoder, TextEncoder, register_counter, register_histogram, Counter, Histogram};
use lazy_static::lazy_static;

lazy_static! {
    static ref HTTP_REQUESTS_TOTAL: Counter = register_counter!(
        "http_requests_total",
        "Total number of HTTP requests made."
    ).unwrap();
    static ref HTTP_REQUEST_DURATION_SECONDS: Histogram = register_histogram!(
        "http_request_duration_seconds",
        "The HTTP request latencies in seconds."
    ).unwrap();
}

async fn serve_metrics(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
    let encoder = TextEncoder::new();
    let metric_families = prometheus::gather();
    let mut buffer = Vec::new();
    encoder.encode(&metric_families, &mut buffer).unwrap();

    Ok(Response::builder()
        .header("Content-Type", encoder.format_type())
        .body(Body::from(buffer))
        .unwrap())
}

async fn handle_request(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
    HTTP_REQUESTS_TOTAL.inc();
    let timer = HTTP_REQUEST_DURATION_SECONDS.start_timer();

    let response = Response::new(Body::from("Hello, World!"));

    timer.observe_duration();
    Ok(response)
}

#[tokio::main]
async fn main() {
    let make_svc = make_service_fn(|_conn| {
        let svc = service_fn(|req: Request<Body>| async {
            match req.uri().path() {
                "/metrics" => serve_metrics(req).await,
                _ => {
                    HTTP_REQUESTS_TOTAL.inc();
                    let timer = HTTP_REQUEST_DURATION_SECONDS.start_timer();
                    
                    let response = Response::new(Body::from("Hello, world!"));
                    
                    timer.observe_duration();
                    Ok::<_, Infallible>(response)
                },
            }
        });
        async move { Ok::<_, Infallible>(svc) }
        // async move { Ok::<_, Infallible>(service_fn(handle_request)) }
    });

    let addr = SocketAddr::from(([0, 0, 0, 0], 8084));
    // let addr = ([0, 0, 0, 0], 8084).into();
    let server = Server::bind(&addr).serve(make_svc);

    println!("Listening on http://{}", addr);

    if let Err(e) = server.await {
        eprintln!("server error: {}", e);
    }
}
